#ifndef _EK_MEM_H_
#define _EK_MEM_H_

/* EKOS 是设计在 IA32 架构上的操作系统, 在此架构上内存以 4KB 为一个内存
 * 页面, 构成 4GB 的内存寻址空间. 本部分负责调度和管理所有的内存分配,
 * 包括虚拟内存和物理内存的分配 */

/* 申请和释放物理页面 */
int  frame_alloc(physaddr_t *addr);
int  frame_alloc_push(physaddr_t *addr);
void frame_free(physaddr_t addr);
void frame_sfree(physaddr_t addr);

int  frame_share(physaddr_t addr);
bool frame_shared_p(physaddr_t addr);
int  frame_unshare(physaddr_t addr);

int frame_inc_ref(physaddr_t addr);
int frame_dec_ref(physaddr_t addr);
int frame_ref(physaddr_t addr);

/* 锁定内存表入口 */
uint32_t lock_pde(uint32_t idx);
void     release_pde(uint32_t idx, uint32_t val);

uint32_t lock_pte(uint32_t ide);
void     release_pte(uint32_t idx, uint32_t val);

/* 虚拟内存的扩展机制 */
/* create 指向的数值若只有权限位则自动创建页面, 否则直接替换 */
/* 可选的参数仅在 create != NULL 的时候有效, 表示是否强制替换 */
int fix_addr(void *addr, uint32_t *create, ...);

int expand_addr_space(void);

/* 实际可用内存的映射 */
#define MEMMAP_MAXCOUNT 256
extern uint32_t   mlayout_count;
extern physaddr_t mlayout_addr[MEMMAP_MAXCOUNT << 1];

/* 测试某物理地址是否有效 */
#define PA_VAILD(addr)  (mlayout_bin_search(addr) & 1 == 0)

int mlayout_bin_search(physaddr_t addr);

/* 指向第一个可用的物理内存页面 */
extern struct frame_t *frame_free_head;
extern struct frame_t *frame_sfree_head;

/* 可用物理页面的索引上限 */
extern size_t         frames_count;
/* 储存物理页面的指针地址 */
extern struct frame_t *frames;

/* This macro takes a kernel virtual address -- an address that points
 * above KERNBASE, where the machine's maximum 256MB of physical
 * memory is mapped -- and returns the corresponding physical address.
 * It panics if you pass it a non-kernel virtual address.
 */
/* #define PADDR(kva)														\ */
/*      ({																	\ */
/* 		  physaddr_t __m_kva = (physaddr_t) (kva);						\ */
/* 		  if (__m_kva < KBASE)											\ */
/* 		  {																\ */
/* 			   kprintf("PADDR called with invalid kva %08lx", __m_kva);	\ */
/* 		  }																\ */
/* 		  __m_kva - KBASE;												\ */
/*      }) */

#define PADDR(kva) ((physaddr_t)(kva) - KBASE)

/* This macro takes a physical address and returns the corresponding
 * kernel virtual address.  It panics if you pass an invalid physical
 * address. */
/* #define KADDR(pa)														\ */
/*      ({																	\ */
/* 		  physaddr_t __m_pa = (pa);										\ */
/* 		  uint32_t __m_ppn = PAGE_NUM(__m_pa);							\ */
/* 		  if (__m_ppn >= pages_count)									\ */
/* 		  {																\ */
/* 			   kprintf("KADDR called with invalid pa %08lx", __m_pa);	\ */
/* 		  }																\ */
/* 		  (void*) (__m_pa + KBASE);										\ */
/*      }) */

#define KADDR(pa) (void *)((pa) + KBASE)

/* 多任务不安全的内存申请和删除 */
int  frame_alloc_unsafe(physaddr_t *addr);
void frame_free_unsafe(physaddr_t addr);

#define GDT_COUNT 6
/* 全局描述符及其伪描述符 */
/* EKOS 只用到了几个基本的全局段描述符, 并且没有使用局部描述符 */
extern struct segdesc_t    gdt[GDT_COUNT];
extern struct pseudodesc_t gdt_pd;

extern pdir_entry_t boot_vpd[];
extern char boot_stack[];

int init_pgdir(pdir_entry_t *pgdir);

/* 通过页表手动转换至物理地址 */
physaddr_t va2pa(void *addr);

/* 获取当前临时页表入口 */
void *get_local_tvpd(void);
void *get_local_tvpt(void);
void *get_local_tpage(void);
struct pcs_t
*get_local_pcs(void);

void *get_tvpd(int lid);
void *get_tvpt(int lid);
void *get_tpage(int lid);
struct pcs_t
*get_pcs(int lid);

/* 以下供 boot 阶段使用*/
#define BOOT_TVPD   ((void *)(KTMP))
#define BOOT_TVPT   ((void *)(KTMP + (1 << PAGE_SHIFT)))
#define BOOT_TPAGE  ((void *)(KTMP + (2 << PAGE_SHIFT)))

/* 清理临时的页表入口 */
void clear_tmp(void);

/* tooltip missing */
uint32_t split_seg(uint32_t s, uint32_t e);
/* tooltip missing */
void clear_area(void *s, void *e);

#endif /* _EK_MEM_H_ */
